<!DOCTYPE html>

<html>
<head>
<title>Building Admin - Editing - QOR5 Document</title>

<meta name='description'>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<base href='/docs/'>

<link href='index.css' rel='stylesheet' type='text/css'>

<script type='text/javascript' defer src='index.js'></script>
</head>

<body>
<div id='app' v-cloak>
<div v-init-context:vars='{hideAside: false}' class='flex h-screen'>
<div class='flex-1 flex flex-col overflow-hidden'>
<div class='flex h-full'>
<aside v-show='!vars.hideAside' id='menuScroller' class='flex flex-col w-80 h-full bg-gray-50 border-r border-gray-200 overflow-y-auto'>
<div class='h-12'><search></search></div>

<ul class='px-0 py-3 mx-0 text-base font-normal list-none text-gray-700'>
<li class='m-0'>
<a href='index.html' id='index.html' onclick='window.storeMenuState("index.html")' class='inline-block px-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Introduction</a>
</li>

<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Getting Started</li>

<li class='m-0'>
<a href='getting-started/one-minute-quick-start.html' id='getting-started/one-minute-quick-start.html' onclick='window.storeMenuState("getting-started/one-minute-quick-start.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>1 Minute Quick Start</a>
</li>

<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Building Admin</li>

<li class='m-0'>
<a href='basics/presets-instant-crud.html' id='basics/presets-instant-crud.html' onclick='window.storeMenuState("basics/presets-instant-crud.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>presets, Instant CRUD</a>
</li>

<li class='m-0'>
<a href='basics/listing.html' id='basics/listing.html' onclick='window.storeMenuState("basics/listing.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Listing</a>
</li>

<li class='m-0'>
<a href='basics/listing-customizations.html' id='basics/listing-customizations.html' onclick='window.storeMenuState("basics/listing-customizations.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Listing Customizations</a>
</li>

<li class='m-0'>
<a href='basics/filter.html' id='basics/filter.html' onclick='window.storeMenuState("basics/filter.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Filters</a>
</li>

<li class='m-0'>
<a href='presets-guide/editing-customizations.html' id='presets-guide/editing-customizations.html' onclick='window.storeMenuState("presets-guide/editing-customizations.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-blue-500'>Editing</a>
</li>

<li class='m-0'>
<a href='basics/brand.html' id='basics/brand.html' onclick='window.storeMenuState("basics/brand.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Brand</a>
</li>

<li class='m-0'>
<a href='basics/menu.html' id='basics/menu.html' onclick='window.storeMenuState("basics/menu.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Menu</a>
</li>

<li class='m-0'>
<a href='presets-guide/detail-page-for-complex-object.html' id='presets-guide/detail-page-for-complex-object.html' onclick='window.storeMenuState("presets-guide/detail-page-for-complex-object.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Detailing</a>
</li>

<li class='m-0'>
<a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
</li>

<li class='m-0'>
<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
</li>

<li class='m-0'>
<a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
</li>

<li class='m-0'>
<a href='presets-guide/role.html' id='presets-guide/role.html' onclick='window.storeMenuState("presets-guide/role.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Role</a>
</li>

<li class='m-0'>
<a href='basics/shortcut.html' id='basics/shortcut.html' onclick='window.storeMenuState("basics/shortcut.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Keyboard Shortcut</a>
</li>

<li class='m-0'>
<a href='basics/confirm-dialog.html' id='basics/confirm-dialog.html' onclick='window.storeMenuState("basics/confirm-dialog.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Confirm Dialog</a>
</li>

<li class='m-0'>
<a href='slug.html' id='slug.html' onclick='window.storeMenuState("slug.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Slug</a>
</li>

<li class='m-0'>
<a href='seo.html' id='seo.html' onclick='window.storeMenuState("seo.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>SEO</a>
</li>

<li class='m-0'>
<a href='activity-log.html' id='activity-log.html' onclick='window.storeMenuState("activity-log.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Activity Log</a>
</li>

<li class='m-0'>
<a href='basics/worker.html' id='basics/worker.html' onclick='window.storeMenuState("basics/worker.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Worker</a>
</li>

<li class='m-0'>
<a href='basics/publish.html' id='basics/publish.html' onclick='window.storeMenuState("basics/publish.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Publish</a>
</li>

<li class='m-0'>
<a href='basics/i18n.html' id='basics/i18n.html' onclick='window.storeMenuState("basics/i18n.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Internationalization</a>
</li>

<li class='m-0'>
<a href='basics/l10n.html' id='basics/l10n.html' onclick='window.storeMenuState("basics/l10n.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Localization</a>
</li>

<li class='m-0'>
<a href='basics/redirection.html' id='basics/redirection.html' onclick='window.storeMenuState("basics/redirection.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Redirection</a>
</li>

<li class='m-0'>
<a href='basics/custom-page.html' id='basics/custom-page.html' onclick='window.storeMenuState("basics/custom-page.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Custom Pages</a>
</li>

<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Web Application</li>

<li class='m-0'>
<a href='advanced-functions/the-go-html-builder.html' id='advanced-functions/the-go-html-builder.html' onclick='window.storeMenuState("advanced-functions/the-go-html-builder.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>The Go HTML builder</a>
</li>

<li class='m-0'>
<a href='basics/page-func-and-event-func.html' id='basics/page-func-and-event-func.html' onclick='window.storeMenuState("basics/page-func-and-event-func.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Page Func and Event Func</a>
</li>

<li class='m-0'>
<a href='basics/layout-function-and-page-injector.html' id='basics/layout-function-and-page-injector.html' onclick='window.storeMenuState("basics/layout-function-and-page-injector.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout Function and Page Injector</a>
</li>

<li class='m-0'>
<a href='vuetify-components/lazy-portals.html' id='vuetify-components/lazy-portals.html' onclick='window.storeMenuState("vuetify-components/lazy-portals.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Lazy Portals</a>
</li>

<li class='m-0'>
<a href='basics/switch-pages-with-push-state.html' id='basics/switch-pages-with-push-state.html' onclick='window.storeMenuState("basics/switch-pages-with-push-state.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Switch Pages with Push State</a>
</li>

<li class='m-0'>
<a href='basics/reload-page-with-a-flash.html' id='basics/reload-page-with-a-flash.html' onclick='window.storeMenuState("basics/reload-page-with-a-flash.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Reload Page with a Flash</a>
</li>

<li class='m-0'>
<a href='basics/partial-refresh-with-portal.html' id='basics/partial-refresh-with-portal.html' onclick='window.storeMenuState("basics/partial-refresh-with-portal.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Partial Refresh with Portal</a>
</li>

<li class='m-0'>
<a href='basics/manipulate-page-url-in-event-func.html' id='basics/manipulate-page-url-in-event-func.html' onclick='window.storeMenuState("basics/manipulate-page-url-in-event-func.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Manipulate Page URL in Event Func</a>
</li>

<li class='m-0'>
<a href='basics/summary-of-event-response.html' id='basics/summary-of-event-response.html' onclick='window.storeMenuState("basics/summary-of-event-response.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Summary of Event Response</a>
</li>

<li class='m-0'>
<a href='basics/web-scope.html' id='basics/web-scope.html' onclick='window.storeMenuState("basics/web-scope.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>web.Scope</a>
</li>

<li class='m-0'>
<a href='basics/event-handling.html' id='basics/event-handling.html' onclick='window.storeMenuState("basics/event-handling.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Event Handling</a>
</li>

<li class='m-0'>
<a href='basics/form-handling.html' id='basics/form-handling.html' onclick='window.storeMenuState("basics/form-handling.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Form Handling</a>
</li>

<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>UI Components</li>

<li class='m-0'>
<a href='vuetify-components/basic-inputs.html' id='vuetify-components/basic-inputs.html' onclick='window.storeMenuState("vuetify-components/basic-inputs.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Basic Inputs</a>
</li>

<li class='m-0'>
<a href='vuetify-components/a-taste-of-using-vuetify-in-go.html' id='vuetify-components/a-taste-of-using-vuetify-in-go.html' onclick='window.storeMenuState("vuetify-components/a-taste-of-using-vuetify-in-go.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>A Taste of using Vuetify in Go</a>
</li>

<li class='m-0'>
<a href='vuetify-components/linkage-select.html' id='vuetify-components/linkage-select.html' onclick='window.storeMenuState("vuetify-components/linkage-select.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Linkage Select</a>
</li>

<li class='m-0'>
<a href='components-guide/composite-new-component-with-go.html' id='components-guide/composite-new-component-with-go.html' onclick='window.storeMenuState("components-guide/composite-new-component-with-go.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Composite new Component With Go</a>
</li>

<li class='m-0'>
<a href='components-guide/integrate-a-heavy-vue-component.html' id='components-guide/integrate-a-heavy-vue-component.html' onclick='window.storeMenuState("components-guide/integrate-a-heavy-vue-component.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Integrate a heavy Vue Component</a>
</li>

<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Appendix</li>

<li class='m-0'>
<a href='appendix/all-demo-examples.html' id='appendix/all-demo-examples.html' onclick='window.storeMenuState("appendix/all-demo-examples.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>All Demo Examples</a>
</li>
</ul>
</aside>

<main class='flex flex-col w-full bg-white overflow-x-hidden overflow-y-auto'>
<div id='docContentBox' class='flex flex-row w-full'>
<div class='flex flex-grow flex-col w-2/3'>
<div class='flex flex-row'>
<button @click='vars.hideAside = !vars.hideAside' class='w-12 h-12 p-4'>
<div class='w-4 h-4 fill-current text-gray-300'>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 2 12 L 2 11 L 14 11 L 14 12 Z M 2 8.5 L 2 7.5 L 14 7.5 L 14 8.5 Z M 2 5 L 2 4 L 14 4 L 14 5 Z M 2 5 "/>
</g>
</svg>
</div>
</button>
</div>

<div id='docMainBox' class='px-16 pb-12 pt-4 overflow-auto'>
<h1 class='mb-8'>Editing</h1>

<div class='border-t'><p>Editing an object will be always in a drawer popup. select which fields can edit for each model
by using the <code>.Only</code> func of <code>EditingBuilder</code>, There are different ways to configure the type
of component that is used to do the editing.</p>

<h2 id='configure-field-for-a-single-model'>Configure field for a single model
<a href='#configure-field-for-a-single-model' class='anchor'></a>
</h2>
<p>Use a customized component is as simple as add the extra asset to the preset instance.
And configure the component func on the field:</p>

<highlightjs :language='"go"' :code='"//\n//go:embed assets\nvar assets embed.FS\n\nfunc PresetsEditingCustomizationDescription(b *presets.Builder, db *gorm.DB) (\n\tmb *presets.ModelBuilder,\n\tcl *presets.ListingBuilder,\n\tce *presets.EditingBuilder,\n\tdp *presets.DetailingBuilder,\n) {\n\tmb, cl, ce, _ = PresetsListingCustomizationBulkActions(b, db)\n\n\tce.Only(\"Name\", \"Email\", \"CompanyID\", \"Description\")\n\n\tce.ValidateFunc(func(obj interface{}, ctx *web.EventContext) (err web.ValidationErrors) {\n\t\tcustomer := obj.(*Customer)\n\t\tif customer.Name == \"\" {\n\t\t\terr.FieldError(\"Name\", \"name must not be empty\")\n\t\t}\n\t\tif customer.Email == \"\" {\n\t\t\terr.FieldError(\"Email\", \"email must not be empty\")\n\t\t}\n\t\tif customer.Description == \"\" {\n\t\t\terr.FieldError(\"Description\", \"description must not be empty\")\n\t\t}\n\t\treturn\n\t})\n\n\tce.Field(\"Description\").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {\n\t\textensions := vx.TiptapSlackLikeExtensions()\n\t\treturn tiptap.TiptapEditor(db, field.Name).\n\t\t\tExtensions(extensions).\n\t\t\tAttr(web.VField(field.FormKey, fmt.Sprint(reflectutils.MustGet(obj, field.Name)))...).\n\t\t\tLabel(field.Label).\n\t\t\tDisabled(field.Disabled).\n\t\t\tErrorMessages(field.Errors...)\n\t})\n\n\t// If you just want to specify the label to be displayed\n\twrapper := presets.WrapperFieldLabel(func(evCtx *web.EventContext, obj interface{}, field *presets.FieldContext) (name2label map[string]string, err error) {\n\t\treturn map[string]string{\n\t\t\t\"Name\":  \"Customer Name\",\n\t\t\t\"Email\": \"Customer Email\",\n\t\t}, nil\n\t})\n\tce.Field(\"Name\").LazyWrapComponentFunc(wrapper)\n\tce.Field(\"Email\").LazyWrapComponentFunc(wrapper)\n\treturn\n}\n"'></highlightjs>

<div>
<div class='demo'>
<a href='/examples/presets-editing-customization-description/customers' target='_blank'>Check the demo</a>
 | 
<a href='https://github.com/qor5/admin/tree/main/docs/docsrc/examples/examples_presets/editing.go#L23-L74' target='_blank'>Source on GitHub</a>
</div>
</div>
<ul>
<li>Added the redactor javascript and css component pack as an extra asset</li>
<li>Configure the description field to use the component func that returns the <code>tiptap.TipTapEditor</code> component</li>
<li>Set the field name and value of the component</li>
</ul>

<h2 id='configure-field-type-for-all-models'>Configure field type for all models
<a href='#configure-field-type-for-all-models' class='anchor'></a>
</h2>
<p>Set a global field type to component func like the following:</p>

<highlightjs :language='"go"' :code='"\ntype MyFile string\n\ntype Product struct {\n\tID        int\n\tTitle     string\n\tMainImage MyFile\n}\n\nfunc PresetsEditingCustomizationFileType(b *presets.Builder, db *gorm.DB) (\n\tmb *presets.ModelBuilder,\n\tcl *presets.ListingBuilder,\n\tce *presets.EditingBuilder,\n\tdp *presets.DetailingBuilder,\n) {\n\tmb, cl, ce, dp = PresetsEditingCustomizationDescription(b, db)\n\terr := db.AutoMigrate(\u0026Product{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tb.FieldDefaults(presets.WRITE).\n\t\tFieldType(MyFile(\"\")).\n\t\tComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {\n\t\t\tval := field.Value(obj).(MyFile)\n\t\t\tvar img h.HTMLComponent\n\t\t\tif len(string(val)) \u003e 0 {\n\t\t\t\timg = h.Img(string(val))\n\t\t\t}\n\t\t\tvar er h.HTMLComponent\n\t\t\tif len(field.Errors) \u003e 0 {\n\t\t\t\ter = h.Div().Text(field.Errors[0]).Style(\"color:red\")\n\t\t\t}\n\t\t\treturn h.Div(\n\t\t\t\timg,\n\t\t\t\ter,\n\t\t\t\th.Input(\"\").Type(\"file\").Attr(\"@change\", fmt.Sprintf(\"form.%s_NewFile = $event.target.files[0]\", field.Name)),\n\t\t\t)\n\t\t}).\n\t\tSetterFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) (err error) {\n\t\t\tff, _, _ := ctx.R.FormFile(fmt.Sprintf(\"%s_NewFile\", field.Name))\n\n\t\t\tif ff == nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tvar req *http.Request\n\t\t\treq, err = http.NewRequest(\"PUT\", \"https://transfer.sh/myfile.png\", ff)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tvar res *http.Response\n\t\t\tres, err = http.DefaultClient.Do(req)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tvar b []byte\n\t\t\tb, err = io.ReadAll(res.Body)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif res.StatusCode == 500 {\n\t\t\t\terr = fmt.Errorf(\"%s\", string(b))\n\t\t\t\treturn\n\t\t\t}\n\t\t\terr = reflectutils.Set(obj, field.Name, MyFile(b))\n\t\t\treturn\n\t\t})\n\n\tpmb := b.Model(\u0026Product{})\n\tpmb.Editing(\"Title\", \"MainImage\")\n\treturn\n}\n"'></highlightjs>

<div>
<div class='demo'>
<a href='/examples/presets-editing-customization-file-type/products' target='_blank'>Check the demo</a>
 | 
<a href='https://github.com/qor5/admin/tree/main/docs/docsrc/examples/examples_presets/editing.go#L109-L183' target='_blank'>Source on GitHub</a>
</div>
</div>
<ul>
<li>We define <code>MyFile</code> to actually be a string</li>
<li>We set <code>FieldDefaults</code> for writing, which is the editing drawer popup to be a customized component</li>
<li>The component show an img tag with the string as src if it&#39;s not empty</li>
<li>The component add a file input for user to upload new file</li>
<li>The <code>SetterFunc</code> is called before save the object, it uploads the file to transfer.sh, and get the url back,
then set the value to <code>MainImage</code> field</li>
</ul>

<p>With <code>FieldDefaults</code> we can write libraries that add customized type for different models to reuse. It can take care
of how to display the edit controls, and How to save the object.</p>

<h2 id='tabs'>Tabs
<a href='#tabs' class='anchor'></a>
</h2>
<p>Tabs can be added by using <code>AppendTabsPanelFunc</code> func on <code>EditingBuilder</code>:</p>

<highlightjs :language='"go"' :code='"\nfunc PresetsEditingCustomizationTabs(b *presets.Builder, db *gorm.DB) (\n\tmb *presets.ModelBuilder,\n\tcl *presets.ListingBuilder,\n\tce *presets.EditingBuilder,\n\tdp *presets.DetailingBuilder,\n) {\n\tb.DataOperator(gorm2op.DataOperator(db))\n\tmb = b.Model(\u0026Company{})\n\tmb.Listing(\"ID\", \"Name\")\n\tmb.Editing().AppendTabsPanelFunc(func(obj interface{}, ctx *web.EventContext) (tab, content h.HTMLComponent) {\n\t\tc := obj.(*Company)\n\t\ttab = v.VTab(h.Text(\"New Tab\")).Value(\"2\")\n\t\tcontent = v.VTabsWindowItem(\n\t\t\tv.VListItemTitle(h.Text(fmt.Sprintf(\"Name: %s\", c.Name))),\n\t\t).Value(\"2\").Class(\"pa-4\")\n\t\treturn\n\t})\n\treturn\n}\n"'></highlightjs>

<div>
<div class='demo'>
<a href='/examples/presets-editing-customization-tabs/companies' target='_blank'>Check the demo</a>
 | 
<a href='https://github.com/qor5/admin/tree/main/docs/docsrc/examples/examples_presets/editing.go#L207-L229' target='_blank'>Source on GitHub</a>
</div>
</div>

<h2 id='validation'>Validation
<a href='#validation' class='anchor'></a>
</h2>
<p>Field level validation and display on field can be added by implement <code>ValidateFunc</code>,
and set the <code>web.ValidationErrors</code> result:</p>

<highlightjs :language='"go"' :code='"\nfunc PresetsEditingCustomizationValidation(b *presets.Builder, db *gorm.DB) (\n\tmb *presets.ModelBuilder,\n\tcl *presets.ListingBuilder,\n\tce *presets.EditingBuilder,\n\tdp *presets.DetailingBuilder,\n) {\n\tmb, cl, ce, _ = PresetsEditingCustomizationDescription(b, db)\n\n\tce.ValidateFunc(func(obj interface{}, ctx *web.EventContext) (err web.ValidationErrors) {\n\t\tcus := obj.(*Customer)\n\t\tif len(cus.Name) \u003c 10 {\n\t\t\terr.FieldError(\"Name\", \"name is too short\")\n\t\t}\n\t\treturn\n\t})\n\treturn\n}\n"'></highlightjs>

<div>
<div class='demo'>
<a href='/examples/presets-editing-customization-validation/customers' target='_blank'>Check the demo</a>
 | 
<a href='https://github.com/qor5/admin/tree/main/docs/docsrc/examples/examples_presets/editing.go#L185-L205' target='_blank'>Source on GitHub</a>
</div>
</div>
<ul>
<li>We validate the <code>Name</code> of the customer must be longer than 10</li>
<li>If the error happens, If will show below the field</li>
</ul>
</div>
</div>
</div>

<div class='font-medium text-base hidden xl:block text-gray-600 pt-4'>
<div class='sticky top-4 w-52'>On This Page<toc></toc></div>
</div>
</div>
<search-result></search-result></main>
</div>
</div>
</div>
</div>
</body>
</html>
